/*
 * BMEditor.cpp
 *
 *  Created on: Oct 29, 2013 
 *      Author: pridhvi
 */
#include "BMEditor.h"
#include <cstring>
#include <sstream>
#include <iomanip>
#include <stdio.h>
#include <iostream>
#include <math.h>
#include <sys/time.h>
#include <unistd.h>
#include "../BufferManager/BufferManager.h"
#include "../dependency/ErrorLookupTable.h"
#include "../dependency/ExternDefsOfGlobalConstants.h"
#include "../Global/GlobalVar.h"
#include "../Utils/Timer.h"
#include "../dependency/ExternDefOfGlobalVariables.h"
Timer exectime;

using namespace std;

BMEditor::BMEditor() {
    // TODO Auto-generated constructor stub

}

BMEditor::~BMEditor() {
    // TODO Auto-generated destructor stub
}

void BMEditor::showEditor() {

    string query;
    while (true) {
        query = "";
        cout << endl << "BM#";
        do {
            string subq;
            cin >> subq;
            if (query.length() > 0)
                query.append(" ");
            query.append(subq);

        } while (query[query.length() - 1] != ';');

        query.resize(query.length() - 1);


        DEBUG("QUERY:" << query);

        if ((query.compare("EXIT") == 0)
                || (query.compare("exit ") == 0)) {
            cout << endl << "exiting Buffer Manager, bye!" << endl;
            break;
        } else {
            int err = execute(query);
            if (err != SUCCESS) {
                cout << "\nSorry Error Occurred :";
            }

        }

    }
}

STATUS_CODE BMEditor::execute(string query) {
    stringstream ss(query);
    string buf;
    vector < string > tokens;
    vector <string> history;
    history.push_back(query);
    while (ss >> buf)
        tokens.push_back(buf);
    string command = tokens[0];
    if (command.compare("HITRATE") == 0) {
        testHitRate();
    } else if (command.compare("CREATEDB") == 0) {
        if (tokens.size() <= 1) {
            cout << "DATABASE_NAME_REQUIRED" << endl;
        } else {
            if (tokens.size() == 3) {
                testCreateDB(tokens[1], atoi(tokens[2].c_str()));
            } else {
                testCreateDB(tokens[1], DEFAULT_NUMBER_PAGES);
            }
        }
    } else if (command.compare("OPENDB") == 0) {
        if (tokens.size() <= 1) {
            cout << "DATABASE_NAME_REQUIRED" << endl;
        } else {
            testOpenDB(tokens[1]);
        }
    } else if (command.compare("SHOWCACHEINDEX") == 0) {
        testCacheIndex();
    } else if (command.compare("HELP") == 0) {
        testDisplayHelp();
    } else if (command.compare("CLOSEDB") == 0) {
        if (tokens.size() < 2)
            testCloseDB();
        else
            testCloseDB(atoi(tokens[1].c_str()));
    } else if (command.compare("DROPDB") == 0) {
        testDropDB(tokens[1].c_str());
    } else if (command.compare("LISTDB") == 0) {
        testListDB();
    } else if (command.compare("RESIZEDB") == 0) {
        testResizeDB(atoi(tokens[1].c_str()));
    } else if (command.compare("METADATA") == 0) {
        testMetaData();
    } else if (command.compare("READPAGE") == 0) { //pg no, @op: data
        if (tokens.size() < 3)
            testReadPage(atoi(tokens[1].c_str()));
        else
            testReadPage(atoi(tokens[1].c_str()), atoi(tokens[2].c_str()));
    } else if (command.compare("WRITEPAGE") == 0) { //pg no, data
        if (tokens.size() < 4)
            testWritePage(atoi(tokens[1].c_str()), tokens[2].c_str());
        else {
            string data = "";
            for (unsigned int i = 3; i < tokens.size(); i++) {
                data = data + tokens[i] + " ";
            }
            DEBUG("Data to be written is:" << data);
            testWritePage(atoi(tokens[1].c_str()), atoi(tokens[2].c_str()), data.c_str());
        }
    } else if (command.compare("STARTCACHE") == 0) { //init buffer
        testStatCache(atoi(tokens[1].c_str()));
    } else if (command.compare("STOPCACHE") == 0) { //del buffer
        testStopCache();
    } else if (command.compare("COMMITCACHE") == 0) { //commit all and delete all = flushall
        testCommitCache();
    } else if (command.compare("SHOWFRAME") == 0) { //@args: frame no, @op: hex
        testShowFrame(atoi(tokens[1].c_str()));
    } else if (command.compare("SHOWBUFFERLIST") == 0) { //frameno file name, pg no dirty bit priority,sort by frame/prioity
        testShowBufferList();
    } else if (command.compare("MEMSTAT") == 0) { //no of frame and mem used
        testDisplayMemory();
    } else {
        cout << "Invalid Command.. press help for seeing commands.." << endl;
    }
    return SUCCESS;
}

void BMEditor::testHitRate() {
    BufferManager *bm = BufferManager::getInstance();
    cout << "Number Of Disk Access:" << bm->noOfDiskAccess << endl;
    cout << "Number Of Pages Read:    " << bm->noOfPgRead << endl;
    cout << "Number Of Times Pages found in Buffer:" << bm->noOfTimesPgFoundInBuffer << endl;
    cout << "Total Request To Buffer:" << bm->totalReqToBuffer << endl;
    cout << "HitRate = NumOfTimesPageFoundInBuffer / TotalRequestsToBuffer" << endl;
    float hitrate = bm->getHitRate();
    if (hitrate != 22)
        cout << "HIT_RATE:" << hitrate << "\n";
    else
        cout << ErrorLookupTable::lookup(hitrate) << endl;
}

void BMEditor::testCreateDB(string dbname, long numOfPages) {
    DEBUG("Inside testCreateDB function.");
    BufferManager *bm = BufferManager::getInstance();
    //exectime.startTimer();
    int err = bm->createDB(dbname, numOfPages);
    //exectime.endTimer();
    if (err != SUCCESS)
        cout << ErrorLookupTable::lookup(err) << endl;
    else
        cout << "CREATE_DATABASE_SUCCESSFULL :" << dbname << endl;
}

void BMEditor::testOpenDB(string dbname) {
    BufferManager *bm = BufferManager::getInstance();
    // exectime.startTimer();
    int err = bm->useDB(dbname);
    //exectime.endTimer();
    if (err != SUCCESS)
        cout << ErrorLookupTable::lookup(err) << endl;
    else
        cout << "OPEN_DATABASE SUCCESSFULL : " << dbname << endl;
}

void BMEditor::testCloseDB() {
    BufferManager *bm = BufferManager::getInstance();
    DEBUG("Inside testCloseDB function.");
    int err = bm->closeDB();
    if (err != SUCCESS)
        cout << ErrorLookupTable::lookup(err) << endl;
    else
        cout << "DATABASE_CLOSED" << endl;
}

void BMEditor::testDropDB(string dbname) {
    BufferManager *bm = BufferManager::getInstance();
    DEBUG("Inside testDropDB function.");
    int err = bm->dropDB(dbname);
    if (err != SUCCESS)
        cout << ErrorLookupTable::lookup(err) << endl;
    else
        cout << "DATABASE_DROPPED_SUCCESSFULLY" << endl;
}

void BMEditor::testResizeDB(int cache_id) {
    BufferManager *bm = BufferManager::getInstance();
    DEBUG("Inside testDropDB function.");
    int err = bm->resizeDB(cache_id);
    if (err != SUCCESS)
        cout << ErrorLookupTable::lookup(err) << endl;
    else
        cout << "DATABASE_DROPPED_SUCCESSFULLY" << endl;
}

void BMEditor::testListDB() {
    BufferManager *bm = BufferManager::getInstance();
    // cout<<"Files list in folder:"<<DATABASE_FOLDER<<endl;
    map<string, int> f = bm->listDB();
    if (f.empty()) {
        cout << "NO_DATABASE_EXISTS" << endl;
    } else {
        cout << "-----------------------------------------------" << endl;
        cout << "|" << setiosflags(ios::left) << setw(30) << "DATABASE_NAME" << "|" << resetiosflags(ios::left) << setw(15) << "DATABASE_SIZE" << "|" << endl;
        cout << "-----------------------------------------------" << endl;
        for (map<string, int> ::iterator itr = f.begin(); itr != f.end(); itr++) {
            cout << "|" << setiosflags(ios::left) << setw(30) << itr->first << "|" << resetiosflags(ios::left) << setw(15) << itr->second << "|" << endl;
        }
        cout << "-----------------------------------------------" << endl;
    }

}

void BMEditor::testDisplayMemory() {
    cout << "Total memory allocated:" << TOTAL_MEMORY_ALLOCATED << endl;
    cout << "Total memory deallocated:" << TOTAL_MEMORY_DEALLOCATED << endl;

}

void BMEditor::testShowBufferList() {
    BufferManager *bm = BufferManager::getInstance();
    int err = bm->displayCache();
    if (err != SUCCESS) {
        cout << ErrorLookupTable::lookup(err) << endl;
    } else {
        cout << "\n";
    }
}

void BMEditor::testShowFrame(int frameNo) {
    BufferManager *bm = BufferManager::getInstance();
    if (IS_CACHE_ON) {
        bm->displayframe(frameNo);
        showPage(bm->bufferPool_[frameNo]->pageData);
    } else
        cout << "CACHE_IS_OFF" << endl;
}

void BMEditor::testCommitCache() {
    BufferManager *bm = BufferManager::getInstance();
    int err = bm->commitCache();
    if (err != SUCCESS) {
        cout << ErrorLookupTable::lookup(err) << endl;
    } else {
        cout << "CACHE_COMMITTED" << endl;
    }
}

void BMEditor::testStopCache() {
    DEBUG("Inside Stop Cache BMEditor");
    BufferManager *bm = BufferManager::getInstance();
    int err = bm->stopCache();
    if (err != SUCCESS) {
        cout << ErrorLookupTable::lookup(err) << endl;
    } else {
        cout << "CACHE_STOPPED" << endl;
    }
}

void BMEditor::testStatCache(int sizeMB) {
    DEBUG("Inside Start Cache BMEditor");
    BufferManager *bm = BufferManager::getInstance();
    int err = bm->startCache(sizeMB);
    if (err != SUCCESS) {
        cout << ErrorLookupTable::lookup(err) << endl;
    } else {
        cout << "CACHE_STARTED" << endl;
    }
}

void BMEditor::testWritePage(int pgNo, const char* data) {
    BufferManager *bm = BufferManager::getInstance();
    char * pgData;
    //exectime.startTimer();
    bm->getPage(pgNo, pgData);
    strcpy(pgData, data);
    bm->releasePage(pgNo, true);
    float hitrate = bm->getHitRate();
    cout << "HIT_RATE:" << hitrate << "\n";
    //exectime.endTimer();
    //bm->releasePage(pgNo, false);
}

void BMEditor::testReadPage(int pgNo) {
    char* pageData;
    BufferManager *bm = BufferManager::getInstance();
    //exectime.startTimer();
    int err = bm->getPage(pgNo, pageData);
    //exectime.endTimer();
    if (err != SUCCESS) {
        cout << ErrorLookupTable::lookup(err) << endl;
    } else {
        showPage(pageData);
        bm->releasePage(pgNo, false);
        float hitrate = bm->getHitRate();
        cout << "HIT_RATE:" << hitrate << "\n";
    }
}

void BMEditor::showPage(char * pageData) {

    //long total_rows = ceil(PAGE_SIZE / 16);
    int count = 0;
    char go = 'N';
    int init = 0;
    while (go == 'N') {
        ostringstream display;
        display << "Address \t Hexadecimal Values \t\t\t Data Value\t" << endl;
        display.clear();
        display << "\n";
        for (int i = init; i < init + 16 && count < PAGE_SIZE; i++) {
            display << setfill('0') << setw(6); // for setting the first 6 bits to 0
            display << hex << i << "\t "; //for getting the hex value of address
            ostringstream actualChar; // for . display
            for (int j = i * 16, k = 0; k < 16 && count < PAGE_SIZE; k++, j++) {
                display << setfill('0') << setw(2);
                int val = (int) pageData[j]; //for converting into 8 bits
                val = val & 0xFF; // for getting the right most 8 bits
                if (isalnum(val)) { // for checking the printable character
                    actualChar << pageData[j]; // copying printable characters
                } else {
                    actualChar << ".";
                }
                actualChar << " ";
                display << hex << val << " "; // for getting the hex values  of printable characters
                count++;
            }
            display << "  " << actualChar.str();
            actualChar.clear(); // clearing for next round
            display << "\n";
        }
        cout << display.str();
        init = init + 16;
        if (init == 256) {
            break;
        }
        // total_rows=total_rows/16;
        cout << "Press (N)ext or (E)xit:";
        cin >> go;
    }
    //cout << display.str(); //for displaying in the string format
}

/**************************Functions with Cache_id********************************************/
void BMEditor::testCloseDB(int cache_id) {
    BufferManager *bm = BufferManager::getInstance();
    cout << "Inside testCloseDB function." << endl;
    int err = bm->closeDB(cache_id);
    if (err != SUCCESS)
        cout << ErrorLookupTable::lookup(err) << endl;
    else
        cout << "Successfully closed Database" << endl;

}

void BMEditor::testReadPage(int cache_id, int pgNo) {
    char* pageData;
    BufferManager *bm = BufferManager::getInstance();
    //exectime.startTimer();
    int err = bm->getPage(cache_id, pgNo, pageData);
    //exectime.endTimer();
    if (err != SUCCESS) {
        cout << ErrorLookupTable::lookup(err) << endl;
    } else {
        showPage(pageData);
        bm->releasePage(cache_id, pgNo, false);
        float hitrate = bm->getHitRate();
        if (hitrate != 22)
            cout << "HIT_RATE:" << hitrate << "\n";
        else
            cout << ErrorLookupTable::lookup(hitrate) << endl;
    }

}

void BMEditor::testWritePage(int cache_id, int pgNo, const char* data) {
    BufferManager *bm = BufferManager::getInstance();
    char * pgData;
    //exectime.startTimer();
    bm->getPage(cache_id, pgNo, pgData);
    strcpy(pgData, data);
    int err = bm->releasePage(cache_id, pgNo, true);
    //exectime.endTimer();
    if (err != SUCCESS)
        cout << ErrorLookupTable::lookup(err) << endl;
    else {
        cout << "SUCCESSFULLY_WRITTEN" << endl;
        float hitrate = bm->getHitRate();
        if (hitrate != 22)
            cout << "HIT_RATE:" << hitrate << "\n";
        else
            cout << ErrorLookupTable::lookup(hitrate) << endl;
    }
}

void BMEditor::testCacheIndex() {
    DiskManager *disk = new DiskManager();
    int err = disk->displayCacheIndex();
    if (err != SUCCESS)
        cout << ErrorLookupTable::lookup(err) << endl;
}

void BMEditor::testMetaData() {
    BufferManager *bm = BufferManager::getInstance();
    int free = 0, occupied = 0;
    if (IS_CACHE_ON) {
        cout << "*************************************************BUFFER META DATA*************************************************" << endl;
        cout << "Total Number Of Frames Allocated For Buffer are :" << bm->noOfFrames << endl;
        for (int i = 0; i < bm->noOfFrames; i++) {
            if (bm->bufferPool_[i]->pageNo == -1)
                free += 1;
            else
                occupied += 1;
        }
        cout << "Number Of Free Frames are :" << free << endl;
        cout << "Number Of Occupied Frames are :" << occupied << endl;
        cout << "Each Frame Size :" << PAGE_SIZE << endl;
        cout << "Total Buffer Size :" << bm->bufferSize_ << endl;
        cout << "Total Hit Rate :" << bm->getHitRate() << endl;
        cout << "******************************************************************************************************************" << endl;
    } else {
        cout << "CACHE_IS_OFF" << endl;
    }
}

void BMEditor::testDisplayHelp() {
    cout << "*************************************************BUFFERMANAGER HELP*************************************************" << endl;
    cout << "Commands available are:" << endl;
    cout << endl;
    cout << "1. startcache NUM; :- command to start cache with 'NUM' of frames." << endl;
    cout << "2. stopcache; :- command to stop cache." << endl;
    cout << "3. commitcache; :- command to commit all the dirty frames to database." << endl;
    cout << "4. createdb NAME [NUMOFPAGES]; :- command to create a database by 'NAME' and other argument is optional and default value is 1000" << endl;
    cout << "5. opendb NAME; :- command to open a particular database." << endl;
    cout << "6. showcacheindex; :- command to see the number of databases opened and their respective _fd." << endl;
    cout << "7. readpage CONTEXTID PAGENUM; :- command to read the page data from the respective database as in CONTEXTID." << endl;
    cout << "8. writepage CONTEXTID PAGENUM 'DATA'; :- command to write in a page data to a database as in CONTEXTID with the DATA value." << endl;
    cout << "9. closedb CONTEXTID; :- command to close the database as in CONTEXTID." << endl;
    cout << "10. showbufferlist; :- command to display the contents in the buffer." << endl;
    cout << "11. showframe #num; :- command to display the contents in a particular frame." << endl;
    cout << "12. metadata; :- command to display the metadata about the buffer." << endl;
    cout << "13. hitrate; :- command to display the hitrate." << endl;
    cout << "14. listdb; :- command to display all available databases." << endl;
    cout << "15. dropdb NAME; :- command to delete respective database." << endl;
    cout << "16. exit; :- command to exit from BufferManager." << endl;
    cout << endl;
    cout << "********************************************************************************************************************" << endl;
}